//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.planw.beetl.sql;

import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiReferenceParameterList;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.ui.JBColor;
import com.intellij.util.Consumer;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import com.planw.beetl.model.TreeNode;
import com.planw.beetl.service.BeetlMapperService;
import com.planw.beetl.service.BeetlSqlService;
import com.planw.beetl.utils.BeetlSqlConst;
import com.planw.beetl.utils.ConstUtil;
import com.planw.beetl.utils.PsiKt;
import com.planw.beetl.utils.StrUtil;

import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.intellij.plugins.markdown.lang.MarkdownElementTypes;
import org.jetbrains.annotations.NotNull;

public class BeetlSqlCompletionProvider  extends CompletionProvider<CompletionParameters> {
    private static final Logger logger = Logger.getInstance(BeetlSqlCompletionProvider.class);

    BeetlSqlCompletionProvider() {
    }

    @Override
    protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
        if (parameters == null) {
            return;
        }

        if (context == null) {
            return;
        }

        if (result == null) {
            return;
        }

        if (!ConstUtil.isReady()) {
            logger.info("项目未准备完成");
        } else {
            PsiFile currentPsiFile = parameters.getOriginalFile();
            BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(currentPsiFile);
            String word = (String)Optional.of(parameters).map(CompletionParameters::getOriginalPosition).map(PsiElement::getText).orElse("");
            Editor editor = parameters.getEditor();
            Caret caret = editor.getCaretModel().getPrimaryCaret();
            VisualPosition visualPosition = caret.getVisualPosition();
            int column = visualPosition.column;
            word = StrUtil.findBeetlSqlVarSubStr(word, column, beetlSqlConst);
            if (StringUtil.isEmptyOrSpaces(word)) {
                logger.info("未找到正在输入的变量，跳过代码完成");
            } else {
                Project project = currentPsiFile.getProject();
                PsiElement positionPsi = parameters.getPosition();
                BeetlSqlService beetlSqlService = BeetlSqlService.getInstance(project);
                InjectedLanguageManagerImpl ilm = InjectedLanguageManagerImpl.getInstanceImpl(project);
                boolean injectedFragment = ilm.isInjectedFragment(currentPsiFile);
                logger.info("本次代码完成是否是内嵌代码段：" + injectedFragment);
                PsiElement curNode;
                if (injectedFragment) {
                    curNode = ilm.getInjectionHost(positionPsi);
                } else {
                    curNode = positionPsi;
                }

                String sqlId = this.findSqlId(curNode);
                logger.info("所属的SqlId：" + sqlId);
                if (!StringUtils.isBlank(sqlId)) {
                    PsiFile mdPsiFile = ilm.getTopLevelFile(positionPsi);
                    PsiClass mapper = beetlSqlService.findMapperClass(mdPsiFile.getOriginalFile());
                    logger.info(String.format("通过SqlId【%s】找到mapper类【%s】 ", sqlId, mapper));
                    if (mapper != null) {
                        PsiMethod[] methods = mapper.findMethodsByName(sqlId, true);
                        if (ArrayUtils.isEmpty(methods)) {
                            logger.warn(String.format("没有找到匹配SqlId【%s】的mapper方法 ", sqlId));
                            return;
                        }

                        logger.info("对文本[" + word + "]代码完成");
                        if (StringUtils.endsWith(word, ".")) {
                            word = word + "*";
                        }

                        String[] searchStrs = word.split("\\.");
                        if (ArrayUtils.isNotEmpty(searchStrs)) {
                            result = result.withPrefixMatcher(searchStrs[searchStrs.length - 1]);
                            logger.info("进入匹配过程之前 ");
                            this.addResultSet(currentPsiFile, result, methods, searchStrs);
                            logger.info("进入匹配过程之后");
                            for (Map.Entry<String, LookupElement> entry : ConstUtil.DEFAULT_FUNC_CACHE.entrySet()) {
                                String key = entry.getKey();
                                LookupElement value = entry.getValue();
                                result.addElement(value);
                            }
                            for (Map.Entry<String, LookupElement> entry : ConstUtil.EXT_FUNC_CACHE.entrySet()) {
                                String k = entry.getKey();
                                LookupElement v = entry.getValue();
                                result.addElement(v);
                            }
                        }
                    }

                }
            }
        }
    }

    private String findSqlId(PsiElement curNode) {
        logger.info("【beetlsql】 findSqlId before curNode" + curNode);

        do {
            IElementType parentElementType = (IElementType)Optional.ofNullable(curNode).map(PsiElement::getParent).map(PsiElement::getNode).map(ASTNode::getElementType).orElse(null);
            if (MarkdownElementTypes.MARKDOWN_FILE_ELEMENT_TYPE.equals(parentElementType)) {
                logger.info("【beetlsql】 findSqlId after curNode" + curNode);
                return (String)Optional.ofNullable(curNode).map((x) -> PsiTreeUtil.findSiblingBackward(x, MarkdownElementTypes.SETEXT_1, (Consumer)null)).map(PsiTreeUtil::firstChild).map(PsiTreeUtil::firstChild).map(PsiElement::getText).orElse(null);
            }

            curNode = (PsiElement)Optional.ofNullable(curNode).map(PsiElement::getParent).orElse(null);
        } while(curNode != null);

        return null;
    }

    private void addResultSet(PsiFile psiFile, CompletionResultSet result, PsiMethod[] methods, String[] searchStrs) {
        BeetlMapperService beetlMapperService = BeetlMapperService.getInstance(psiFile.getProject());
        BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(psiFile);
        String rootAnnotationClassName = beetlSqlConst.ROOT_BEETL_CLASS;
        String pageQueryClassName = beetlSqlConst.PAGE_QUERY_BEETL_CLASS;
        String paramAnnotationClassName = beetlSqlConst.PARAM_BEETL_CLASS;
        TreeNode root = new TreeNode();

        for(PsiMethod method : methods) {
            PsiParameter[] psiParameters = method.getParameterList().getParameters();
            int parameterSize = psiParameters.length;

            for(PsiParameter parameter : psiParameters) {
                if (!beetlMapperService.isPageRequest(pageQueryClassName, parameter)) {
                    Queue<String> searchStrsQueue = new ArrayBlockingQueue(searchStrs.length, false, Arrays.asList(searchStrs));
                    TreeNode child = new TreeNode();
                    root.getChildren().add(child);
                    if (searchStrsQueue.size() == 1 && PsiKt.isPrimitive(parameter.getType())) {
                        String search = (String)searchStrsQueue.poll();
                        this.addPrimitiveParameter(child, parameter, search);
                    } else {
                        boolean rootParameter = this.isRootParameter(rootAnnotationClassName, paramAnnotationClassName, parameter, parameterSize);
                        if (!rootParameter) {
                            String tempSearch = (String)searchStrsQueue.peek();
                            String path = beetlMapperService.getParamAnnotationValue(paramAnnotationClassName, parameter);
                            if (StringUtils.isBlank(path)) {
                                path = parameter.getName();
                            }

                            if (StrUtil.searchCharSequence(path, tempSearch)) {
                                child.setType(parameter.getType());
                                child.setPath(path);
                                child.setPsiElement(parameter.getOriginalElement());
                                child.setShow(true);
                                searchStrsQueue.poll();
                            }
                        }

                        this.dsfParameterTypeFields(pageQueryClassName, child, parameter, searchStrsQueue);
                    }
                }
            }
        }

        this.handleTreeNodeToLookupElement(result, root);
    }

    private boolean isRootParameter(String rootAnnotationClassName, String paramAnnotationClassName, PsiParameter parameter, int parameterSize) {
        if (!StringUtil.isEmptyOrSpaces(paramAnnotationClassName)) {
            return false;
        } else if (parameterSize == 1) {
            return true;
        } else {
            PsiAnnotation root = parameter.getAnnotation(rootAnnotationClassName);
            return root != null;
        }
    }

    private void addPrimitiveParameter(TreeNode child, PsiParameter parameter, String search) {
        BeetlMapperService beetlMapperService = BeetlMapperService.getInstance(parameter.getProject());
        String text = beetlMapperService.getParamAnnotationValue(parameter);
        if (StringUtils.isBlank(text)) {
            text = parameter.getName();
        }

        if (StrUtil.searchCharSequence(text, search)) {
            child.setType(parameter.getType());
            child.setPath(parameter.getName());
            child.setPsiElement(parameter.getOriginalElement());
            child.setShow(true);
        }

    }

    private void dsfParameterTypeFields(String pageQueryClassName, TreeNode child, PsiParameter parameter, Queue<String> searchStrQueue) {
        String parameterClassName = (String)Optional.ofNullable(PsiUtil.resolveClassInType(parameter.getType())).map(PsiClass::getQualifiedName).orElse("");
        PsiClass entityPsiClass;
        if (StringUtils.equals(pageQueryClassName, parameterClassName)) {
            PsiTypeElement[] typeParameterElements = (PsiTypeElement[])Optional.ofNullable(parameter.getTypeElement()).map(PsiTypeElement::getInnermostComponentReferenceElement).map(PsiJavaCodeReferenceElement::getParameterList).map(PsiReferenceParameterList::getTypeParameterElements).orElse(new PsiTypeElement[0]);
            PsiType entityType = (PsiType)Arrays.stream(typeParameterElements).findFirst().map(PsiTypeElement::getType).orElse(null);
            entityPsiClass = PsiUtil.resolveClassInType(entityType);
        } else {
            entityPsiClass = PsiUtil.resolveClassInType(parameter.getType());
        }

        PsiField[] fields = (PsiField[])Optional.ofNullable(entityPsiClass).map(PsiClass::getFields).orElse(new PsiField[0]);
        if (!ArrayUtils.isEmpty(fields)) {
            this.resolvePsiClass(child, Arrays.asList(fields), searchStrQueue);
        }
    }

    private void resolvePsiClass(TreeNode topNode, List<PsiField> fields, Queue<String> searchQueue) {
        String searchStr = (String)searchQueue.poll();
        if (!StringUtils.isBlank(searchStr)) {
            for(PsiField field : fields) {
                if (StrUtil.searchCharSequence(field.getName(), searchStr)) {
                    TreeNode node = new TreeNode();
                    node.setPsiElement(field.getOriginalElement());
                    node.setPath(field.getName());
                    node.setShow(true);
                    node.setType(field.getType());
                    topNode.addChild(node);
                    PsiType fieldType = field.getType();
                    if (!PsiKt.isPrimitive(fieldType)) {
                        PsiClass fieldPsiCls = PsiUtil.resolveClassInType(fieldType);
                        PsiField[] fieldPsiClsFields = (PsiField[])Optional.ofNullable(fieldPsiCls).map(PsiClass::getFields).orElse(new PsiField[0]);
                        this.resolvePsiClass(node, Arrays.asList(fieldPsiClsFields), searchQueue);
                    }
                }
            }

        }
    }

    private void handleTreeNodeToLookupElement(CompletionResultSet result, TreeNode treeNode) {
        this.dsfTreeNode(result, treeNode.getChildren(), "");
    }

    private void dsfTreeNode(CompletionResultSet result, List<TreeNode> children, String lookupStr) {
        for(TreeNode child : children) {
            String tempLookupStr = lookupStr + child.getPath() + ".";
            List<TreeNode> childChildren = child.getChildren();
            if (!childChildren.isEmpty()) {
                this.dsfTreeNode(result, childChildren, tempLookupStr);
            } else if (child.isShow()) {
                if (tempLookupStr.startsWith(".")) {
                    tempLookupStr = tempLookupStr.substring(1);
                }

                if (tempLookupStr.endsWith(".")) {
                    tempLookupStr = tempLookupStr.substring(0, tempLookupStr.length() - 1);
                }

                LookupElement lookupElement = LookupElementBuilder.createWithSmartPointer(tempLookupStr, child.getPsiElement()).withPresentableText(tempLookupStr).withCaseSensitivity(true).withIcon(PlatformIcons.VARIABLE_ICON).withItemTextForeground(JBColor.BLACK).bold().withTypeText(child.getType().getCanonicalText(), PlatformIcons.VARIABLE_ICON, true).withTypeIconRightAligned(true).withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE);
                logger.info("【beetlsql】CompletionResultSet " + lookupElement.getLookupString());
                result.addElement(lookupElement);
            }
        }

    }
}
